//
//      Copyright Timothy Evans 2014 2015    timothy.evans@gsecraif.org
//
//      This file is part of gsecraif.
//
//      gsecraif is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 3 of the License, or
//      (at your option) any later version.
//
//      See gsecraif.cpp
//
// =====================================================================
//
//! Functions to combine component files.
//
// =====================================================================
//
// Compile with g++

#include "comb_functions.h"

using namespace std;

extern unsigned short int debuglevel;
extern bool showhashes;

// function to combine files:
                    
void combine_files(int numoffiles, unsigned short int rrotate, bool transpose, 
                   vector<string> &compfilenames) { 


unsigned long int bytescount = 0;       // how many bytes have been read, 
                                        // progress counter
unsigned long int totalbytes = 0;       // grand total of bytes read
const char *std_out= "/dev/stdout";     // reference to Standard Out
string postfix = "000";                 // 3 character postfix
string compfilename;                    // component file name - prefixpostfix
ifstream infile[255];                   // component files for input
char fileformatversion=1;               // component file format version
unsigned char missingfiles = 0;         // count of missing componet files, 
                                        // RAID 5 can survive the loss of one
        // if this goes over 1 it is not possible to combine the files

unsigned short int missingfileid = 999; // if a file is missing this will
                                        // hold it's id

                                        // file id goes from 0 to 255
char ch;                                // holds each byte as it is read
                                        // from the input file
vector<unsigned char> compmap;          // mapping of component files
// To allow component files to be given in any order -
// e.g. split alpha beta gamma delta (IDs 0,1,2,3)
// combine delta beta alpha gamma (IDs 3,1,0,2)
// compmap would hold 3,1,0,2

deque<Stripe> buffbytes;

Stripe buffstripe(numoffiles);            // one of the stripes above
Stripe procbuffstripe(numoffiles);        // one of the stripes above
vector<unsigned char> header;                // array of bytes from the header
                                             // of each component

unsigned short int stripestart = 0;     // where in the "array" the
                                        // stripe starts
bool finished = false;                  // flag to show if finished
                                        // reading / writing data
int stripeposition=0;
bool paritymissing = false;             // flag used in reconstruction
int bytestoread = 2;                    // how many bytes need to be read at
                                        // a time, 
                                        // one less than number of files,
                                        // default 2
vector<unsigned char> origbytes;        // slice - array of bytes processed
                                        // from read in data
vector<unsigned char> bit_rotatedbytes; // slice - array of bit rotated bytes
unsigned short int smissing = 999;      // position is a stripe of
                                        // missing element
unsigned short int padbytes = 0;        // how many padding bytes there are
unsigned short int checkpadbytes = 999; // used to check padbytes consistent
                                        // between component files
int howmanyfiles = 0;                   // number of files according to
                                        // header info
                                        // in each component file
int whichfile = 0;                      // component file id according to header
                                        // info in each component file
int rotationvalue = 0;                  // rotation according to header info
                                        // in each component file
bool file_format_ok = true;             // flag to show if format of 
                                        // component files is correct

bytestoread = numoffiles -1;

unsigned short int chvalue; // int value of ch for debug

// Initialize the slice vectors:
for (int compfileid=0; compfileid != (numoffiles - 1); ++compfileid) {
    origbytes.push_back(0);
    bit_rotatedbytes.push_back(0);
    }

// Initialize the other vectors:    
for (int compfileid=0; compfileid != numoffiles; ++compfileid) {
    header.push_back(0);
    compmap.push_back(0);
    }


// Is transpose used?
if (transpose) {
	fileformatversion=2;
	rrotate=0;
    }


// open standard out:
ofstream out(std_out, ios::out | ios::binary); // output to standard out
if(!out) {
    cerr << ERR_STDOUT;
    return;
    }
    
if (debuglevel > 1) cerr << "Standard out open.\n";
// -----

// open input files:


for (unsigned int compfileid=0; compfileid <= (numoffiles - 1); ++compfileid) {
    // Note: open() function needs C style string for file name
    // hence .c_str()	
    infile[compfileid].open(compfilenames[compfileid].c_str(),
                            ios::out | ios::binary);  // input
    if(!infile[compfileid]) {
        ++missingfiles;
        missingfileid=compfileid;
        if (debuglevel) {
            cerr << ERR_OPEN_IN_FILE << compfilenames[missingfileid] << "\n";
            }
        }
    } // for (compfileid=0..

if (missingfiles > 1) {
    cerr << missingfiles << ERR_MULTIPLE_IN;
    return;
    }
if (debuglevel > 1) cerr << "Input files open.\n";
// -----


// Read and verify the file headers:
for (unsigned short int compfileid=0; compfileid <= (numoffiles - 1);
     ++compfileid) {
    if (compfileid != missingfileid) {
        infile[compfileid].get(ch); // file format version
        header[compfileid]= (unsigned char) ch;
        } // if (compfileid !=..
        
    } // for (compfileid=0..
    
if ( ! check_comp_file_format_version(numoffiles, missingfileid, 
                                      header, fileformatversion ) ) {
    file_format_ok = false;
    }
   
for (unsigned short int compfileid=0; compfileid <= (numoffiles - 1);
     ++compfileid) {
    if (compfileid != missingfileid) {    
        infile[compfileid].get(ch); // number of componet files
        header[compfileid]= (unsigned char) ch;
        } // if (compfileid !=..
        
    } // for (compfileid=0..
   
if ( ! check_comp_count(numoffiles, missingfileid, header ) ) {
    file_format_ok = false;
    }
   
for (unsigned short int compfileid=0; compfileid <= (numoffiles - 1);
     ++compfileid) {
    if (compfileid != missingfileid) {   
        infile[compfileid].get(ch); // identity of file
        header[compfileid]=ch;
        compmap[compfileid]=ch;
        } // if (compfileid !=..
        
    } // for (compfileid=0..

if ( ! check_which_comp(numoffiles, missingfileid, header ) ) {
    file_format_ok = false;
    }
    
for (unsigned short int compfileid=0; compfileid <= (numoffiles - 1);
     ++compfileid) {
    if (compfileid != missingfileid) {    
        infile[compfileid].get(ch); // rotation
        header[compfileid]=ch;
        } // if (compfileid !=..
        
    } // for (compfileid=0..

if ( ! check_rotate(numoffiles, missingfileid, header ) ) {
    file_format_ok = false;
    }

// header read
if (debuglevel > 1) cerr << "Headers read.\n----------\n";

if ( ! file_format_ok ) {
    cerr << ERR_FORMAT;
    // fatal
    return;
  }
// -----

// Now read first stripe; 
// if this fails there is no trailer and the component file(s) are corrupt.
// parity_position is already set to end of array by constructor
stripestart = 0;

read_stripe(numoffiles, missingfileid, compmap, finished, buffstripe, infile );

if (finished) {
    cerr << ERR_FILE_CORRUPT;
    return;
    }

// Copy read stripe to queue:      
buffbytes.push_back(buffstripe);

if (debuglevel > 1) cerr << "First stripe read.\n";
// -----

// Read the second strip; 
// if this gets EOF, the first stripe was the trailer and
// components were empty (which is OK)
// and the original file was null (which is OK)
// if this does not get EOF, the first stripe was data

read_stripe(numoffiles, missingfileid, compmap, finished, buffstripe, infile );
buffstripe.decrease_parity_pos();  // move parity position back
if (finished) {
    // previous stripe was trailer
    if (debuglevel > 1) cerr << "null file\n";
    return;
    }

// Copy read stripe to queue:    
buffbytes.push_back(buffstripe);

if (debuglevel > 1) cerr << "Second stripe read.\n----------\n";
// -----

// Now read the rest of the stripes from the component files: 

while (!finished) {
    // Read the next stripe:
    read_stripe(numoffiles, missingfileid, compmap, finished,
                buffstripe, infile );
    buffstripe.decrease_parity_pos();  // move parity position back
    // Attempting to read the next stripe may result in [finished] being set
    // if not finished process the front:
    stripeposition=stripestart;
    if (!finished) {
        if (debuglevel > 1) cerr << "Next stripe read.\n";
    
        // Copy read stripe to back of queue:
        buffbytes.push_back(buffstripe);        
        // Copy front of queue to procbuffstripe to be processed:
        procbuffstripe = buffbytes.front();
        buffbytes.pop_front(); // remove from queue
        if (debuglevel > 1) {
            cerr << "\t\t procbuffslice:\n";
            for (unsigned short int compfileid=0; compfileid<=(numoffiles-1); 
                 ++compfileid)
                {
                chvalue = (unsigned char) procbuffstripe.get_byte(compfileid);               
                cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
                }
            chvalue = procbuffstripe.position_of_parity();
            cerr << "\t\t Parity pos: " << chvalue << "\n";    
            }
        // If there is a missing file, do the repair:    
        if (missingfileid != 999) 
            procbuffstripe.repair_stripe(missingfileid);
            if (debuglevel > 1) {
                cerr << "\t\t missing file id" << missingfileid << "\n";
                cerr << "\t\t procbuffslice repaird:\n";
                for (unsigned short int compfileid=0;
                     compfileid<=(numoffiles-1); ++compfileid)
                    {
                    chvalue = (unsigned char) procbuffstripe.get_byte(compfileid);                    
                    cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
                    }
                chvalue = procbuffstripe.position_of_parity();
                cerr << "\t\t Parity pos: " << chvalue << "\n";    
                }     
        // "de-stripe" - see tech notes :
        procbuffstripe.de_rotate_stripe(stripeposition);
        if (debuglevel > 1) {
            cerr << "\t\t procbuffslice de-rotated:\n";
            for (unsigned short int compfileid=0; compfileid<=(numoffiles-1);
                 ++compfileid)
                {
                chvalue = (unsigned char) procbuffstripe.get_byte(compfileid);                
                cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
                }
            chvalue = procbuffstripe.position_of_parity();
            cerr << "\t\t Parity pos: " << chvalue << "\n";    
            }
        if (debuglevel > 1) cerr << "\t\t Stripe processed.\n";    
        // get slice:
        bit_rotatedbytes = procbuffstripe.slice_from_stripe();
        if (debuglevel > 1) {
            cerr << "\t\t bit_rotatedbytes:\n";
            for (unsigned short int compfileid=0; compfileid<(numoffiles-1);
                 ++compfileid) {
                chvalue = (unsigned char) bit_rotatedbytes[compfileid];
                cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
                }	
           }

        if (!transpose) {
            // De-Bit-Rotate:
            bit_rotate(bit_rotatedbytes, origbytes, (numoffiles-1),
                       (-1 * rrotate));
		    }
		else {
			bit_transpose(bit_rotatedbytes, origbytes,
                                      (numoffiles-1), true);
		}
			    
        if (debuglevel > 1) {
            cerr << "\t\t orig bytes:\n";
                for (unsigned short int compfileid=0;
                     compfileid<(numoffiles-1); ++compfileid) {
                chvalue = (unsigned char) origbytes[compfileid];
                cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
                }	
            }
        // Output to standard out (recovered file):
        for (unsigned short int compfileid=0; compfileid<=(bytestoread-1);
             ++compfileid) {
            out.put(origbytes[compfileid]);
            ++bytescount;
            }
        
        // output progress:    
        if (bytescount > 1024) {
            if (debuglevel > 1) cerr << "totalbytes: " << totalbytes
                                     << " bytescount: " << bytescount << "\n";
            totalbytes += 1024;
            bytescount = bytescount % 1024;
            if (showhashes) cerr << "#";
            }
        // Keep track of the start position of each stripe:    
        ++stripestart;
        // Move the head and tail of the queue forward:
        if (stripestart > (numoffiles-1)) stripestart = 0;
        } // if (!finished
        
    } // while (!finished...
    
// -----


// process the trailer:

if (debuglevel > 1) {
    cerr << "\t\t ==  trailer==\n";  
    }

// The final stripe to be read should be the trailer containing the
// number of pad bytes
procbuffstripe = buffbytes.back();


for (unsigned short int compfileid=0; compfileid<=(numoffiles-1);
     ++compfileid) {
    if (compfileid != missingfileid) {
        padbytes=procbuffstripe.get_byte(compfileid);        
        if (debuglevel > 1) cerr << "Pad: " << padbytes << "\n";
        if (checkpadbytes != 999) {
            if ((padbytes != checkpadbytes) || (padbytes > numoffiles)) {
                cerr << ERR_PAD_BYTE << compfileid << ERR_PAD_VALUE
                     << padbytes << "\n";
                }
                
            } // if (checkpadbytes !=...
            
        checkpadbytes=padbytes;
        } // if (compfileid...
        
    }  // for (unsigned...
    
stripeposition=stripestart;

// assign last values read to procbuffstripe
procbuffstripe = buffbytes.front();


if (debuglevel > 1) {
    cerr << "\t\t procbuffslice:\n";
        for (unsigned short int compfileid=0; compfileid<=(numoffiles-1);
             ++compfileid) {
            chvalue = (unsigned char) procbuffstripe.get_byte(compfileid);    
            cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
            } 
        chvalue = procbuffstripe.position_of_parity();
        cerr << "\t\t Parity pos: " << chvalue << "\n";    
        }
// If there is a missing file, do the repair:    
if (missingfileid != 999) { 
    procbuffstripe.repair_stripe(missingfileid);
    if (debuglevel > 1) {
        cerr << "\t\t missing file id" << missingfileid << "\n";
        cerr << "\t\t procbuffslice repaird:\n";
        for (unsigned short int compfileid=0; compfileid<=(numoffiles-1);
             ++compfileid) {
            chvalue = (unsigned char) procbuffstripe.get_byte(compfileid);            
            cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
            }
        chvalue = procbuffstripe.position_of_parity();
        cerr << "\t\t Parity pos: " << chvalue << "\n";    
        } 
    }

// "de-stripe" - see tech notes :
procbuffstripe.de_rotate_stripe(stripeposition);
if (debuglevel > 1) {
    cerr << "\t\t procbuffslice de-rotated:\n";
    for (unsigned short int compfileid=0; compfileid<=(numoffiles-1);
         ++compfileid) {
        chvalue = (unsigned char) procbuffstripe.get_byte(compfileid);        
        cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
        }
    chvalue = procbuffstripe.position_of_parity();
    cerr << "\t\t Parity pos: " << chvalue << "\n"; 
    cerr << "\t\t Stripe processed.\n";       
    }
 
// get slice:
bit_rotatedbytes = procbuffstripe.slice_from_stripe();
if (debuglevel > 1) {
    cerr << "\t\t bit_rotatedbytes:\n";
    for (unsigned short int compfileid=0; compfileid<(numoffiles-1);
         ++compfileid) {
        chvalue = (unsigned char) bit_rotatedbytes[compfileid];
        cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
        }	
    }

if (!transpose) {        
    // De-Bit-rotate:
    bit_rotate(bit_rotatedbytes, origbytes, (numoffiles-1), (-1 * rrotate));
    }
    else {
        bit_transpose(bit_rotatedbytes, origbytes, (numoffiles-1), true);
        }
if (debuglevel > 1) {
    cerr << "\t\t orig bytes:\n";
    for (unsigned short int compfileid=0; compfileid<(numoffiles-1);
        ++compfileid)
        {
        chvalue = (unsigned char) origbytes[compfileid];
        cerr << "\t\t" << compfileid << " : " << chvalue << "\n";
        }	
    }

    
// output to standard out (recovered file):
for (unsigned short int compfileid=0; compfileid<=(bytestoread-1-padbytes);
     ++compfileid)
    {
    out.put(origbytes[compfileid]);
    bytescount++;
    }
    
totalbytes += bytescount;  // update the total bytes
// -----

// close standard out:
out.close();

// close input files:
for (unsigned short int compfileid=0; compfileid<=(numoffiles-1); ++compfileid) {
    infile[compfileid].close();
    }

// files have been combined 


// Print some basic information if debug level > 0
if (debuglevel) {
    cerr << "\n";
    cerr << totalbytes << " Bytes written.\n";
    cerr << "Files have been combined.\n";
    }

}  // end of file_comb function

// =====================================================================


// check that the file format of each component file is supported
// return true if supported
bool check_comp_file_format_version(int numoffiles,
                                    unsigned short int missingfileid, 
                                    vector<unsigned char> header,
                                    char fileformatversion )
{                                    
//const unsigned char fversion1=1;     // file format version
//const unsigned char fversion2=2;     // file format version
for (unsigned short int compfileid=0; compfileid <= (numoffiles - 1);
     ++compfileid) {
    if (compfileid != missingfileid) {
        // if ((header[compfileid] != fversion1) && (header[compfileid] != fversion2)) {
        if (header[compfileid] != fileformatversion) {			
            cerr << ERR_FORMAT_VERSION;
            return false;
            // fatal
            } // if (header...
            
        } // if (compfileid !=..
    } // for
return true;    
} //end function
// =====================================================================

// check each component file agrees about the number of components
// return true of component files agree
bool check_comp_count(int numoffiles, unsigned short int missingfileid, 
                      vector<unsigned char> header )                      
                 
{
for (unsigned short int compfileid=0; compfileid <= (numoffiles - 1);
     ++compfileid) {
    if (compfileid != missingfileid) {
        if (header[compfileid] != numoffiles) {
            cerr << ERR_INCONSISTENT;
            return false;
            // fatal
            }
            
        } // if (compfileid !=..
        
    } // for
return true;  
}
// =====================================================================

// check each component is unique
// return true if components are unique
bool check_which_comp(int numoffiles, unsigned short int missingfileid,
                      vector<unsigned char> header )                       
{
unsigned char unique_check[255];    // array to check component ids unique
unsigned short int zero_id_count = 0;   // how many components have an id of 0
                                    // should be one
for (unsigned short int compfileid=0; compfileid < (255); ++compfileid)
    unique_check[compfileid]=0; // initialize 

for (unsigned short int compfileid=0; compfileid <= (numoffiles - 1);
     ++compfileid) {
    if (compfileid != missingfileid) {  
        if (header[compfileid] == 0)
            {
            if (zero_id_count > 1) {
                cerr << ERR_DUPLICATE_ZERO;
                return false;
                // fatal    
                }
        else {
            ++zero_id_count;
            }
                
        } // if (header..
        
        if (unique_check[compfileid] != 0) {
            // vulnerable to duplicate zeros, but that is fixed by
            // a special zero count above
            cerr << ERR_DUP_ID;
            return false;
            }
        else {
            unique_check[compfileid]=header[compfileid];
            }
    
        } // if (compfileid !=..
        
    }
return true;
}
// =====================================================================

// Check all the components agree on the bit rotation value (0 to 7)
// return true if component files agree
bool check_rotate(int numoffiles, unsigned short int missingfileid, 
                  vector<unsigned char> header )
                  
{
bool rotate_set = false;
        // has what the bit rotation should be been determined?
        
unsigned char rotation_value = 0;       // what the bit rotation value should be
        // initialize to arbitrary value of zero
        
for (unsigned short int compfileid=0; compfileid <= (numoffiles -1);
     ++compfileid) {
    if (compfileid != missingfileid) {
        if ( rotate_set ) {
            if (header[compfileid] != rotation_value) {
                cerr << ERR_ROT_VALUE;
                return false;
                }
                 
            } // if ( rotate_set...
        else {
            rotate_set = true;
            rotation_value = header[compfileid];
            } // if rotate...
                
        } // if (compfileid !=..
    } // for (unsigned short int compfileid...
return true;    
} // end of function
// =====================================================================

// read a stripe when combining files in to the original
void read_stripe(int numoffiles, unsigned short int missingfileid, 
                 vector<unsigned char> compmap, bool &finished, 
                 Stripe &buffstripe, ifstream *infile )              
{
char ch;    // holds each byte as it is read from the input file
unsigned short int chvalue; // int value of ch for debug
if (debuglevel > 1) cerr << "reading stripe - \n compfileid : byte\n";
for (unsigned short int compfileid=0; compfileid<=(numoffiles-1); ++compfileid) { 
    if (compfileid != missingfileid) {
        infile[compfileid].get(ch); 
        if (infile[compfileid]) {
            buffstripe.put_byte( (compmap[compfileid]), ch);         
            if (debuglevel > 1) {
                chvalue = (unsigned char) buffstripe.get_byte( (compmap[compfileid]) );                
                cerr << compfileid << " : " << chvalue << "\n";
                }
                
            }
        else finished=true;
        } // if (compfileid !=..
    else buffstripe.put_byte(compfileid,0);  // set the value to zero for now,    
                                    //it will be recovered using parity later
    
    } // for (compfileid=0...
}
// =====================================================================
